package top.lshaci.learning.springboot.jpaquerydsl.core;

import cn.hutool.core.lang.SimpleCache;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import com.querydsl.core.types.Expression;
import com.querydsl.core.types.Projections;
import com.querydsl.core.types.QBean;
import com.querydsl.core.types.dsl.EntityPathBase;
import com.querydsl.core.types.dsl.SimpleExpression;

import java.lang.reflect.Field;
import java.util.Optional;

/**
 * DslResultUtil
 *
 * @author liangxu
 */
public class DslResultUtil {

    /**
     * 视图类 - 查询对象 缓存
     */
    private static final SimpleCache<String, QBean<?>> VO_CLASS_BEAN_CACHE = new SimpleCache<>();

    /**
     * 将视图对象类型转换为 DSL 的 QBean, 用于字段查询
     *
     * @param voClass 视图对象类
     * @param <V>     视图类型
     * @return DSL 查询对象
     */
    public static <V> QBean<V> select(Class<V> voClass) {
        DslFrom from = voClass.getAnnotation(DslFrom.class);
        if (from == null) {
            throw DslException.build("请使用注解【{}】标记返回对象【{}】", DslFrom.class.getSimpleName(), voClass.getSimpleName());
        }
        return select(voClass, from.value());
    }

    /**
     * 将视图对象类型转换为 DSL 的 QBean, 用于字段查询
     *
     * @param voClass   视图对象类
     * @param fromClass 查询实体Class
     * @param <V>       视图类型
     * @return DSL 查询对象
     */
    public static <V> QBean<V> select(Class<V> voClass, Class<? extends EntityPathBase<?>> fromClass) {
        String key = StrUtil.format("{}_{}", voClass.getSimpleName(), fromClass.getSimpleName());
        QBean<V> bean = (QBean<V>) VO_CLASS_BEAN_CACHE.get(key);
        if (bean != null) {
            return bean;
        }
        Object obj = DslFromUtil.fromObj(fromClass);
        Field[] fields = ReflectUtil.getFields(voClass);
        Expression[] expressions = new Expression[fields.length];
        for (int i = 0; i < fields.length; i++) {
            Field field = fields[i];
            DslResult dslResult = field.getAnnotation(DslResult.class);
            if (dslResult != null && dslResult.ignore()) {
                continue;
            }
            Optional<DslResult> dslResultOptional = Optional.ofNullable(dslResult);
            Object actualObj = obj;
            if (dslResult != null && dslResult.fromClass() != DslVoidEntity.class) {
                actualObj = DslFromUtil.fromObj(dslResult.fromClass());
            }
            String actualField = dslResultOptional.map(DslResult::field)
                    .filter(StrUtil::isNotBlank)
                    .orElseGet(field::getName);
            expressions[i] = ((SimpleExpression) ReflectUtil.getFieldValue(actualObj, actualField)).as(field.getName());
        }

        bean = Projections.bean(voClass, expressions);
        VO_CLASS_BEAN_CACHE.put(key, bean);
        return bean;
    }
}
